package com.googlecode.perfectable.messageable;

import static com.google.common.base.Preconditions.checkState;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Set;

public class StandardEndpoint implements Endpoint {
	private final Socket socket;
	private final XMLTransmitter transmitter;
	private final XMLReceiver receiver;

	@SuppressWarnings("resource")
	StandardEndpoint(Socket socket, Set<Class<? extends Message>> knownTransmittedClasses,
			Set<Class<? extends Message>> knownReceivedClasses, MessageProcessor<Message> processor) {
		this.socket = socket;
		OutputStream outputStream;
		InputStream inputStream;
		try {
			outputStream = this.socket.getOutputStream();
			inputStream = this.socket.getInputStream();
		}
		catch(IOException e) {
			throw new RuntimeException(e); // TODO Auto-generated catch block
		}
		this.transmitter = new XMLTransmitter(outputStream, knownTransmittedClasses);
		try {
			this.transmitter.open();
		}
		catch(TransmissionException e) {
			throw new RuntimeException(e); // MARK transmitter should be opened elsewere, but receiver tries to read at
			// constructor
		}
		this.receiver = new XMLReceiver(inputStream, this, knownReceivedClasses, processor);
	}

	@Override
	public <T extends Message> void send(T message) throws UnknownMessageException, TransmissionException {
		checkState(!this.socket.isClosed());
		this.transmitter.send(message);
	}

	@Override
	public void receivingLoop() {
		try {
			try {
				this.receiver.open();
				while(!this.socket.isClosed()) {
					this.receiver.cycle();
				}
			}
			catch(ReceptionException | UnknownMessageException e) {
				// MARK good place to log
				// just disconnect client
			}
		}
		finally {
			try {
				close();
			}
			catch(IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public void disconnect() {
		try {
			close();
		}
		catch(IOException e) {
			throw new RuntimeException(e); // TODO Auto-generated catch block
		}
	}
	
	public void close() throws IOException {
		if(this.socket.isClosed()) {
			return;
		}
		try {
			this.transmitter.close();
		}
		finally {
			this.socket.close();
		}
	}

}
